use crate::DATE_TIME_FORMAT;
use dashmap::DashMap;
use rusqlite::{Connection, Error};
use std::path::Path;
use std::sync::OnceLock;
use time::format_description::BorrowedFormatItem;
use time::{format_description, OffsetDateTime};
use tracing::info;

pub static SUBSCRIBE: OnceLock<DashMap<String, UserSub>> = OnceLock::new();

static FORMAT_DESC: OnceLock<Vec<BorrowedFormatItem>> = OnceLock::new();

#[derive(Debug)]
pub struct UserSub {
    // key: path, value: urls
    pub sub: DashMap<String, Vec<String>>,
}

pub fn delete(root_dir: &str, username: &str, watch_path: &str) -> rusqlite::Result<String> {
    let conn = Connection::open(root_dir.to_owned() + "/nftp.db3")?;
    let map = SUBSCRIBE.get().unwrap();

    conn.execute(
        "DELETE FROM user_subscribe WHERE username = ?1 AND watch_path = ?2",
        [username, watch_path],
    )
        .map(|row| {
            if row == 1 {
                match map.get(username) {
                    Some(entry) => {
                        let user_sub = &entry.sub;
                        user_sub.remove(watch_path);
                        if user_sub.is_empty() {
                            drop(entry);
                            map.remove(username);
                        }
                    }
                    None => (),
                }

                format!("Unsubscribe {} successful for {}", watch_path, username)
            } else {
                format!("{} isn't subscribed by {}", watch_path, username)
            }
        })
}

pub fn insert(
    root_dir: &str,
    username: String,
    watch_path: String,
    receive_url: Vec<String>,
) -> rusqlite::Result<String> {
    let conn = Connection::open(root_dir.to_owned() + "/nftp.db3")?;
    let map = SUBSCRIBE.get().unwrap();

    if map.contains_key(&username) {
        for entry in &map.get(&username).unwrap().sub {
            let key = entry.key();
            if key.len() > watch_path.len() && key.contains(&watch_path) {
                return Err(Error::InvalidParameterName(format!(
                    "Child path {} already subscribed. \
                            if you want to subscribe it's parent path {}, \
                            please unsubscribe {} first.",
                    key, &watch_path, key
                )));
            }
        }

        let user_sub = &map.get_mut(&username).unwrap().sub;
        let mut p = Some(Path::new(&watch_path));
        while let Some(parent) = p {
            let key = parent.to_str().unwrap();
            if user_sub.contains_key(key) {
                let res = conn.execute(
                    "UPDATE user_subscribe SET receive_url = ?1 WHERE username = ?2 AND watch_path = ?3",
                    (&receive_url.join(","), &username, key),
                );
                info!(
                    "Update subscribe {} successful for {}, new watch path {}",
                    &watch_path,
                    &username,
                    &receive_url.join(",")
                );
                user_sub.insert(key.into(), receive_url);
                return res.map(|_| format!("Update {} successful for {}", &watch_path, &username));
            }
            p = parent.parent();
        }
    }

    let now_local = OffsetDateTime::now_local().unwrap_or(OffsetDateTime::now_utc());
    let create_time = now_local.format(FORMAT_DESC.get().unwrap()).unwrap();
    let res = conn.execute(
        "INSERT INTO user_subscribe (username, watch_path, receive_url, create_time) VALUES (?1, ?2, ?3, ?4)",
        (&username, &watch_path, &receive_url.join(","), create_time),
    );

    let message = format!("Subscribe {} successful for {}", &watch_path, &username);
    let sub = DashMap::new();
    sub.insert(watch_path, receive_url);
    let user_sub = UserSub { sub };
    map.insert(username, user_sub);
    res.map(|_| message)
}

pub fn init(root_dir: &str) -> rusqlite::Result<()> {
    let _ = SUBSCRIBE.get_or_init(|| DashMap::new());

    let _ = FORMAT_DESC
        .get_or_init(|| format_description::parse_borrowed::<1>(DATE_TIME_FORMAT).unwrap());

    let path = Path::new(root_dir).join("nftp.db3");
    let conn = Connection::open(path)?;

    match conn.execute(
        "CREATE TABLE IF NOT EXISTS user_subscribe (
            id    INTEGER PRIMARY KEY,
            username  TEXT NOT NULL,
            watch_path  TEXT NOT NULL,
            receive_url  TEXT NOT NULL,
            create_time TEXT NOT NULL,
            expire_time TEXT DEFAULT '',
            UNIQUE(username, watch_path)
        )",
        (),
    ) {
        Ok(_) => {
            let map = SUBSCRIBE.get().unwrap();

            let mut stmt =
                conn.prepare("SELECT username,watch_path,receive_url FROM user_subscribe")?;
            let rows = stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?;

            for row in rows {
                let row = row?;
                let username: String = row.0;
                let watch_path: String = row.1;
                let receive_url: String = row.2;
                let receive_urls = receive_url
                    .split(',')
                    .map(|s| s.into())
                    .collect::<Vec<String>>();
                match map.get(&username) {
                    Some(entry) => {
                        let user_sub = &entry.sub;
                        user_sub.insert(watch_path, receive_urls);
                    }
                    None => {
                        let sub = DashMap::new();
                        sub.insert(watch_path, receive_urls);
                        let user_sub = UserSub { sub };
                        map.insert(username, user_sub);
                    }
                }
            }
        }
        Err(e) => return Err(e),
    };

    Ok(())
}
